
#pragma once        // 防止头文件被重复包含
#include "Dict.hpp" // 引入自定义的日志头文件
#include "Log.hpp"
#include "InetAddr.hpp" // 引入自定义的网络地址封装头文件

#include <iostream>     // 用于标准输入输出流
#include <string>       // 用于字符串操作
#include <cerrno>       // 用于错误码处理 (errno)
#include <cstring>      // 用于C风格字符串操作 (如 strerror)
#include <cstdlib>      // 用于通用工具函数 (如 exit)
#include <strings.h>    // 用于一些传统的字符串函数 (如 bzero)
#include <sys/types.h>  // 提供基本系统数据类型定义
#include <sys/socket.h> // 提供套接字编程相关函数和数据结构
#include <arpa/inet.h>  // 提供IP地址转换函数 (如 inet_addr, htons)
#include <netinet/in.h> // 提供Internet地址族相关数据结构 (如 sockaddr_in)
#include <functional>   // 用于 std::function 和 std::bind

// echo server -> client -> server // 这是一个描述数据流向的注释
// 定义一些错误码枚举，方便错误处理
enum
{
    SOCKET_ERROR = 1, // 套接字相关的错误
    BIND_ERROR,       // 绑定地址相关的错误
    USAGE_ERROR       // 程序使用方法相关的错误 (如命令行参数错误)
};

// 定义一个静态常量，表示无效或未初始化的文件描述符
const static int defaultfd = -1;

// 下面等价于 typedef std::function<std::string(const std::string&)> func_t;
//  使用类型别名 func_t 定义一个函数对象类型，
//  该函数对象接收一个 const std::string& (客户端请求) 和一个 bool& (操作成功标志) 作为参数，
//  并返回一个 std::string (服务器响应)。
//  这是服务器核心业务逻辑处理函数的签名。
using func_t = std::function<std::string(const std::string &, bool &ok)>;

// 定义 UdpServer 类，用于封装UDP服务器的功能
class UdpServer
{
public:
    // 构造函数
    // port: 服务器监听的端口号
    // func: 业务逻辑处理函数的回调
    UdpServer(uint16_t port, func_t func)
        : _sockfd(defaultfd), // 初始化套接字文件描述符为默认无效值
          _port(port),        // 初始化端口号
          _isrunning(false),  // 初始化服务器运行状态为false
          _func(func)         // 初始化回调函数
    {
    }

    // 初始化服务器，包括创建套接字和绑定地址
    void InitServer()
    {
        // 1. 创建udp socket 套接字 --- 必须要做的
        // AF_INET: 指定地址族为 IPv4
        // SOCK_DGRAM: 指定套接字类型为 UDP (数据报套接字)
        // 0: 指定协议，对于 SOCK_DGRAM 通常为0，让系统自动选择合适的UDP协议
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        // 检查套接字是否创建成功
        if (_sockfd < 0)
        {
            // 如果创建失败，记录致命错误日志并退出程序
            LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);
            exit(SOCKET_ERROR);
        }
        // 记录套接字创建成功的信息日志
        LOG(INFO, "socket create success, sockfd: %d\\n", _sockfd);

        // 2.0 填充sockaddr_in结构，用于指定服务器的地址和端口
        struct sockaddr_in local;      // struct sockaddr_in 系统提供的数据类型。local是变量，用户栈上开辟空间。int a = 100; a = 20;
        bzero(&local, sizeof(local));  // 将local结构体内存清零，这是一个较旧的函数，推荐使用memset
        local.sin_family = AF_INET;    // 设置地址族为 IPv4
        local.sin_port = htons(_port); // 设置端口号，htons将主机字节序的端口号转为网络字节序
        // port要经过网络传输给对面，先到网络，_port：主机序列-> 主机序列，转成网络序列
        // a. 字符串风格的点分十进制的IP地址转成 4 字节IP
        // b. 主机序列，转成网络序列
        // in_addr_t inet_addr(const char *cp) -> 同时完成 a & b
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // "192.168.3.1" -> 字符串风格的点分十进制的IP地址 -> 4字节IP
        // 将服务器IP地址设置为 INADDR_ANY，表示监听本机所有网络接口上的连接请求。
        // htonl(INADDR_ANY) 是可选的，因为INADDR_ANY通常已经是网络字节序的0。
        local.sin_addr.s_addr = INADDR_ANY; // htonl(INADDR_ANY);

        // 2.1 bind sockfd和网络信息(IP(?) + Port)
        // 将创建的套接字 _sockfd 与填充好的本地地址信息 local 进行绑定
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        // 检查绑定是否成功
        if (n < 0)
        {
            // 如果绑定失败，记录致命错误日志并退出程序
            LOG(FATAL, "bind error, %s, %d\\n", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        // 记录套接字绑定成功的信息日志
        LOG(INFO, "socket bind success\\n");
    }

    // 启动服务器，开始接收和处理客户端请求
    void Start() // 所有的服务器，本质解决的是输入输出的问题！不想让网络通信模块和业务模块进行强耦合
    {
        // 一直运行，直到管理者不想运行了， 服务器都是死循环
        // UDP是面向数据报的协议
        _isrunning = true; // 设置服务器运行状态为true
        // 进入主循环，持续处理客户端请求
        while (true)
        {
            char request[1024];           // 定义接收缓冲区，大小为1024字节
            struct sockaddr_in peer;      // 定义sockaddr_in结构体，用于存储发送方（客户端）的地址信息
            socklen_t len = sizeof(peer); // 必须初始化成为sizeof(peer)，用于recvfrom函数正确填充客户端地址长度

            // 1. 我们要让server先收数据
            // 从套接字接收数据。这是一个阻塞操作，直到收到数据为止。
            // _sockfd: 服务器的套接字文件描述符
            // request: 接收数据的缓冲区
            // sizeof(request) - 1: 接收缓冲区的最大长度，减1是为了留出空间存放字符串结束符'\0'
            // 0: 标志位，通常为0
            // (struct sockaddr *)&peer: 指向客户端地址结构的指针，recvfrom会填充这个结构
            // &len: 指向客户端地址结构长度的指针，recvfrom会填充实际长度
            ssize_t n = recvfrom(_sockfd, request, sizeof(request) - 1, 0, (struct sockaddr *)&peer, &len);

            // 检查是否成功接收到数据
            if (n > 0) // n是实际接收到的字节数
            {
                request[n] = 0; // 在接收到的数据末尾添加字符串结束符，使其成为一个C风格字符串

                // 使用InetAddr类封装客户端的地址信息，方便获取IP和端口
                InetAddr addr(peer);
                // 记录调试日志，包含客户端的IP、端口和接收到的消息内容
                LOG(DEBUG, "get message from [%s:%d]: %s\\n", addr.Ip().c_str(), addr.Port(), request);

                bool ok; // 定义一个布尔变量，用于接收业务逻辑处理的成功状态
                // 调用注册的回调函数 _func 处理接收到的请求 (request)
                // ok 参数会被回调函数修改，以指示操作是否成功
                std::string response = _func(request, ok); // 将请求，回调出去，在外部进行数据处理
                // (void)ok;  有的编译器定义了不使用会报错，所以加一个(void)假装使用了一下

                // 2. 我们要将server收到的数据，发回给对方
                // 将处理结果 (response) 通过UDP发送回客户端
                // _sockfd: 服务器的套接字文件描述符
                // response.c_str(): 指向要发送数据的指针
                // response.size(): 要发送数据的长度
                // 0: 标志位，通常为0
                // (struct sockaddr *)&peer: 指向目标客户端地址结构的指针 (从recvfrom获取)
                // len: 目标客户端地址结构的长度 (从recvfrom获取)
                sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&peer, len);
            }
        }
        _isrunning = false; // 理论上，如果循环能退出，则设置服务器运行状态为false (但在当前死循环设计下不会执行到)
    }

    // 析构函数，目前为空，套接字通常在程序结束时由操作系统自动关闭
    // 如果有其他需要清理的资源，可以在这里添加代码
    ~UdpServer()
    {
    }

private:
    int _sockfd; // 套接字文件描述符
    // std::string _ip; // 暂时这样写，这个地方不是必须的.TODO (服务器IP，当前版本未使用，直接绑定INADDR_ANY)
    uint16_t _port;  // 服务器所用的端口号
    bool _isrunning; // 标记服务器是否正在运行

    // 给服务器设定回调，用来让上层进行注册业务的处理方法
    func_t _func; // 存储业务逻辑处理函数的回调对象
};
